package com.gc.cscott.fs;

import com.gc.cscott.domain.contest.TestCase;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.gc.cscott.util.CscottUtil;
import com.gc.cscott.domain.Competitor;
import com.gc.cscott.domain.Executable;
import com.gc.cscott.domain.Platform;
import com.gc.cscott.domain.PlatformsList;
import com.gc.cscott.domain.Solution;
import com.gc.cscott.domain.contest.Contest;
import com.gc.cscott.domain.contest.Source;
import com.gc.cscott.domain.contest.Task;
import com.google.inject.Inject;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.DirectoryWalker;
import org.apache.commons.io.FileUtils;

/**
 * Performs all filesystem related jobs.
 *
 * @author root
 */
public class FileSystemWorker {
    private static final Logger log = Logger.getLogger(FileSystemWorker.class.getSimpleName());

    private static final Pattern solutionDirNamePattern = Pattern.compile("[Zz]([0-9]+)");

    @Inject
    Contest contest;

    @Inject
    PlatformsList platformsList;
    
    private AtomicInteger sequence = new AtomicInteger(0);

    public List<Competitor> listCompetitorsAll() {
        List<Competitor> resultList = new ArrayList<Competitor>();
        for (Source source : contest.getEnabledSourcesList()) {
            resultList.addAll(listCompetitors(source.getLocationDir()));
        }
        return ImmutableList.copyOf(resultList);
    }

    public List<File> listCompetitorsDirsAll() throws IOException {
        final List<File> resultList = new ArrayList<File>();
        for (Competitor competitor : listCompetitorsAll()) {
            resultList.addAll(listFilesAll(competitor.getBasedir()));
        }
        return ImmutableList.copyOf(resultList);
    }

    public List<File> listFilesAll(File dir) throws IOException {
        final List<File> resultList = new ArrayList<File>();
        new DirectoryWalker() {
            @Override
            protected void handleFile(File file, int depth, Collection results) throws IOException {
                resultList.add(file);
            }

            public void walk(File baseDir) throws IOException {
                walk(baseDir, ImmutableList.of());
            }
        }.walk(dir);
        return resultList;
    }

    public List<File> listCompetitorsDirs(Competitor competitor) throws IOException {
        final List<File> resultList = new ArrayList<File>();
        new DirectoryWalker() {
            @Override
            protected boolean handleDirectory(File directory, int depth, Collection results) throws IOException {
                resultList.add(directory);
                return true;
            }

            public void walk(File baseDir) throws IOException {
                walk(baseDir, ImmutableList.of());
            }
        }.walk(competitor.getBasedir());
        return ImmutableList.copyOf(resultList);

    }

    public List<File> listCompetitorsFilesAll() throws IOException {
        final List<File> resultList = new ArrayList<File>();
        for (Competitor competitor : listCompetitorsAll()) {
            new DirectoryWalker() {
                @Override
                protected void handleFile(File file, int depth, Collection results) throws IOException {
                    resultList.add(file);
                }

                public void walk(File baseDir) throws IOException {
                    walk(baseDir, ImmutableList.of());
                }
            }.walk(competitor.getBasedir());
        }
        return ImmutableList.copyOf(resultList);
    }

    public List<Competitor> listCompetitors(File dir) {
        Preconditions.checkArgument(dir.exists(), "Passed directory path is not presented on file system: " + dir.getAbsolutePath());
        Preconditions.checkArgument(dir.isDirectory(), "Passed path is not a directory path: " + dir.getAbsolutePath());

        List<Competitor> result = new ArrayList<Competitor>();
        for (File subdir : dir.listFiles()) {
            if (subdir.isDirectory() == true) {
                result.add(new Competitor(subdir));
            } else {
                log.log(Level.FINE, "Unknown file ''{0}'' in competitors directory ignored.", subdir.getName());
            }
        }
        Collections.sort(result);
        return ImmutableList.copyOf(result);
    }

    public Integer getTaskNumberBySolutionDirectory(File dir) {
        Matcher matcher = solutionDirNamePattern.matcher(dir.getName());
        if (matcher.matches() == true) {
            String taskNumberAsString = matcher.group(1);
            int taskNumber = Integer.parseInt(taskNumberAsString);
            if (taskNumber > 0) {
                return taskNumber;
            }
        }
        return null;
    }

    public List<Solution> listSolutions(Competitor competitor) {
        Preconditions.checkNotNull(competitor);
        List<Solution> result = new ArrayList<Solution>();
        for (File solutionDir : competitor.getBasedir().listFiles()) {
            if (solutionDir.isDirectory()) {
                Integer taskNumber = getTaskNumberBySolutionDirectory(solutionDir);
                if (taskNumber !=null && taskNumber>0) {
                    result.add(new Solution(solutionDir, taskNumber));
                } else {
                    log.log(Level.SEVERE, "Wrong task number mark in solution dir name: {0}", solutionDir.getAbsolutePath());
                }
            } else {
                log.log(Level.FINE, "Uknown file ''{0}'' in competitor private directory: {1}", new Object[]{solutionDir.getName(), competitor.getBasedir().getAbsolutePath()});
            }
        }
        Collections.sort(result);
        return ImmutableList.copyOf(result);
    }

    protected enum FileKind {
        Executable,
        Source;

    }

    protected Executable findFileInSolution(Solution solution, final FileKind fileKind) throws FileNotFoundException {
        Preconditions.checkNotNull(solution);
        Preconditions.checkNotNull(fileKind);

        List<Executable> resultList = new ArrayList<Executable>();
        for (File file : solution.getSolutionBase().listFiles()) {
            if (file.isFile() == true) {
                for (Platform platform : platformsList) {
                    if (fileKind == FileKind.Executable) {
                        if (platform.matchesExecutable(file.getName()) == true) {
                            resultList.add(new Executable(file, platform));
                            break;
                        }
                    } else if (fileKind == FileKind.Source) {
                        if (platform.matchesSource(file.getName()) == true) {
                            resultList.add(new Executable(file, platform));
                            break;
                        }
                    } else {
                        throw new IllegalArgumentException("Unknown kind: "+fileKind);
                    }
                }
            } else {
                log.log(Level.FINE, "Unknown subdirectory ''{0}'' in solution directory: {1}", new Object[]{file.getName(), solution.getSolutionBase().getAbsolutePath()});
            }
        }

        if (resultList.size() == 1) {
            return resultList.get(0);
        } else if (resultList.isEmpty() == true) {
            throw new FileNotFoundException("No "+fileKind+" found in: " + solution.getSolutionBase().getAbsolutePath());
        } else {
            throw new FileNotFoundException("More than one "+fileKind+" found in: " + solution.getSolutionBase().getAbsolutePath()
                    + "  - " + Joiner.on(",\n  - ").join(resultList));
        }
    }

    public Executable findSourceInSolution(Solution solution) throws FileNotFoundException {
        return findFileInSolution(solution, FileKind.Source);
    }

    public Executable findExecutableInSolution(Solution solution) throws FileNotFoundException {
        return findFileInSolution(solution, FileKind.Executable);
    }

    public File createWorkingDirectory(File basedir) throws IOException {
        File workDir = new File(basedir, "work" + CscottUtil.currentTimeString()+"-"+sequence.incrementAndGet());
        if (workDir.mkdir() == false) {
            throw new IOException("Can't create work dir: " + workDir.getAbsolutePath());
        } else {
            log.log(Level.FINE, "Created working directory: {0}", workDir.getAbsolutePath());
            return workDir;
        }
    }

    public void eraseWorkingDirectory(File workDir) {
        try {
            FileUtils.deleteDirectory(workDir);
            log.log(Level.FINE, "Working directory eraised: {0}", workDir.getAbsolutePath());
        } catch (IOException e) {
            log.log(Level.SEVERE, "Failed to remove work dir: " + workDir.getAbsolutePath(), e);
        }
    }

    public File getTaskDir(Task task) {
        return new File(contest.getTaskDir(), task.getRelativeDir());
    }

    public File getTestCaseDir(TestCase testCase) {
        return new File(getTaskDir(testCase.getTask()), String.format("%02d", testCase.getNumber().intValue()));
    }

    public File getTestCaseInputSourceFile(TestCase testCase) {
        return new File(getTestCaseDir(testCase), testCase.getTask().getInputFile());
    }

    public File getTestCaseValidationFile(TestCase testCase) {
        return new File(getTestCaseDir(testCase), testCase.getTask().getValidationFile());
    }

    public void copyTestCaseInputIntoWorkingDirectory(TestCase testCase, File workingDir) throws IOException {
        File testInputSource = getTestCaseInputSourceFile(testCase);
        File testInputTarget = new File(workingDir, testCase.getTask().getInputFile());
        FileUtils.copyFile(testInputSource, testInputTarget);
        log.log(Level.FINE, "copied file {0} -> {1}", new Object[]{testInputSource.getAbsolutePath(), testInputTarget.getAbsolutePath()});
    }

    public File getSolutionOutputFile(Task task, File workingDir) {
        return new File(workingDir, task.getOutputFileName());
    }

    public String loadOutputFileFromResults(Competitor competitor, TestCase testCase) throws IOException{
        File baseCompetitor = new File(contest.getResultDir(),competitor.getName());
        File baseOut = new File(baseCompetitor,"out");
        File storedOutputFile = new File(baseOut,String.format("output-today-T%02d-t%02d.txt", 
                testCase.getTask().getNumber().intValue(),
                testCase.getNumber().intValue()));
        return FileUtils.readFileToString(storedOutputFile);
    }

}
